#include <unistd.h>
#include <cassert>
#include "TaskFile.hpp"
#include <sys/wait.h>
#include <sys/types.h>

const int num = 5;
Task t;

class EndPoint // 用于记录子进程和管道属性
{
public:
    EndPoint(int id, int fd)
        : _child_id(id), _write_fd(fd)
    {
        char namebuffer[64];
        snprintf(namebuffer, sizeof(namebuffer), "编号（%d）process[%d:%d]", number++, id, fd); // process->0[id,fd]
        _processname = namebuffer;
    }

    const std::string &name()
    {
        return _processname;
    }

public:
    int _child_id;            // 子进程pid
    int _write_fd;            // 管道描述符
    std::string _processname; // 进程名称
private:
    static int number; // 计数器
};

int EndPoint::number = 0;

void WaitCommand()
{
    // 执行任务是持续的
    while (true)
    {
        int command = 0;
        int n = read(0, &command, sizeof(int)); // 从标准输入中读取4字节大小的内容，存储到command变量中
        if (n == sizeof(int))
        {
            // 执行任务
            t.Execute(command); //传入读取到的任务编号
        }
        else if (n == 0)
        {
            // 读取到文件末尾，子进程退出
            std::cout << "父进程要我退出，我就退出了！" << std::endl;
            break;
        }
        else
        {
            // 读取错误
            break;
        }
    }
}

int ShowBorad()
{
    int commit = 0;

    std::cout << "------------------------------------" << std::endl;
    std::cout << "|    0.日志任务       1.数据库任务   |" << std::endl;
    std::cout << "|    2.网络请求任务    3.退出        |" << std::endl;
    std::cout << "-------------------------------------" << std::endl;
    std::cout << "请选择：";
    std::cin >> commit;

    return commit;
}

void createprocesses(std::vector<EndPoint> *endpoints)
{
    std::vector<int> fds; //记录父进程与管道文件写端的文件描述符

    for (int i = 0; i < num; i++)
    {
        // 1.1 创建管道文件
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n != -1);
        (void)n;

        // 1.2 创建子进程，父写子读
        pid_t id = fork();
        assert(id != -1);

        if (id == 0)
        {
            // child：子进程读取
            close(pipefd[1]);
            for(auto& fd : fds) close(fd); //子进程关闭拷贝来自父进程的写端的文件描述符

            // 重定向，让子进程从标准输入中读取指令
            dup2(pipefd[0], 0);
            // 当管道中没有数据，子进程会一直等父进程写入，写了才能读取
            WaitCommand();

            close(pipefd[0]);
            exit(0); // 执行完成直接退出
        }
        // parent：关闭没有必要的文件描述符
        close(pipefd[0]);
        endpoints->push_back(EndPoint(id, pipefd[1]));

        fds.push_back(pipefd[1]); //记录父进程的写端的管道文件描述符
    }
}

void ctrlprocess(std::vector<EndPoint> &endpoints)
{
    int cnt = 0;
    while (true)
    {
        // 选择任务
        int command = ShowBorad();
        if (command == 3)
            break;
        if (command < 0 || command > 3)
            continue;

        // 选择进程运行任务
        int index = cnt++;
        cnt %= endpoints.size(); // 防止越界，轮巡选择进程
        std::string name = endpoints[index].name();
        std::cout << "子进程" << name << "执行任务：" << command << std::endl;
        // 下发任务,将任务编号写道管道文件中
        write(endpoints[index]._write_fd, &command, sizeof(int)); // 写入的是任务的编号
        sleep(1);
    }
}

void WaitProcess(std::vector<EndPoint> &endpoints)
{
    // 父进程创建管道文件和子进程，子进程会拷贝父进程大部分属性。包括父进程创建管道时进行写入的端口
    // 关闭管道文件，等待子进程有三种方法

    // 方法一：分别写两个循环，父进程先把管道文件全部关闭，再回收子进程
    // for (const auto &ep : endpoints)close(ep._write_fd);
    // for (const auto &ep : endpoints)
    // {
    //     int id = waitpid(ep._child_id, nullptr, 0);
    //     assert(id != -1);
    //     std::cout << "等待子进程：" << id << "成功" << std::endl;
    // }

    //方法二：一个循环，倒着回收子进程，从最后一个子进程开始，再依次关闭管道的文件描述符和回收子进程
    // for(int end = endpoints.size() - 1; end >= 0; end--)
    // {
    //     close(endpoints[end]._write_fd);
    //     int id = waitpid(endpoints[end]._child_id, nullptr, 0);
    //     assert(id != -1);
    //     std::cout << "等待子进程：" << id << "成功" << std::endl;
    // }

    //方法三：创建管道和进程时，关闭对应子进程拷贝父进程的写的文件描述符
    for(const auto& ep : endpoints)
    {
        close(ep._write_fd);
        int id = waitpid(ep._child_id, nullptr, 0);
        assert(id != -1);
        std::cout << "等待子进程：" << id << "成功" << std::endl;
    }
}

int main()
{
    // 1.构建控制结构
    std::vector<EndPoint> endpoints;
    createprocesses(&endpoints);
    // 2.下发任务，选择子进程
    ctrlprocess(endpoints);
    // 3.关闭管道文件，回收子进程
    WaitProcess(endpoints);

    return 0;
}
